/*
 *  yosys -- Yosys Open SYnthesis Suite
 *
 *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at>
 *
 *  Permission to use, copy, modify, and/or distribute this software for any
 *  purpose with or without fee is hereby granted, provided that the above
 *  copyright notice and this permission notice appear in all copies.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *  ---
 *
 *  A very simple and straightforward frontend for the RTLIL text
 *  representation (as generated by the 'ilang' backend).
 *
 */

#include "ilang_frontend.h"
#include "kernel/register.h"
#include "kernel/log.h"
#include <sys/stat.h>
#include <unistd.h>

void rtlil_frontend_ilang_yyerror(char const *s)
{
	YOSYS_NAMESPACE_PREFIX log_error("Parser error in line %d: %s\n", rtlil_frontend_ilang_yyget_lineno(), s);
}

YOSYS_NAMESPACE_BEGIN

struct IlangFrontend : public Frontend {
	IlangFrontend() : Frontend("ilang", "read modules from ilang file") { }
	void help() override
	{
		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
		log("\n");
		log("    read_ilang [filename]\n");
		log("\n");
		log("Load modules from an ilang file to the current design. (ilang is a text\n");
		log("representation of a design in yosys's internal format.)\n");
		log("\n");
		log("    -nooverwrite\n");
		log("        ignore re-definitions of modules. (the default behavior is to\n");
		log("        create an error message if the existing module is not a blackbox\n");
		log("        module, and overwrite the existing module if it is a blackbox module.)\n");
		log("\n");
		log("    -overwrite\n");
		log("        overwrite existing modules with the same name\n");
		log("\n");
		log("    -lib\n");
		log("        only create empty blackbox modules\n");
		log("\n");
	}
	dict<IdString, char*> ids;

	const char *get_id(IdString n)
	{
		if (ids.count(n) == 0) {
			std::string str = log_id(n);
			for (int i = 0; i < GetSize(str); i++) {
				if (str[i] == '\\')
					str[i] = '/';
			}
			ids[n] = strdup(str.c_str());
		}
		return ids[n];
	}
	void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
	{
		ILANG_FRONTEND::flag_nooverwrite = false;
		ILANG_FRONTEND::flag_overwrite = false;
		ILANG_FRONTEND::flag_lib = false;

		log_header(design, "Executing ILANG frontend.\n");

		size_t argidx;
		for (argidx = 1; argidx < args.size(); argidx++) {
			std::string arg = args[argidx];
			if (arg == "-nooverwrite") {
				ILANG_FRONTEND::flag_nooverwrite = true;
				ILANG_FRONTEND::flag_overwrite = false;
				continue;
			}
			if (arg == "-overwrite") {
				ILANG_FRONTEND::flag_nooverwrite = false;
				ILANG_FRONTEND::flag_overwrite = true;
				continue;
			}
			if (arg == "-lib") {
				ILANG_FRONTEND::flag_lib = true;
				continue;
			}
			break;
		}
		extra_args(f, filename, args, argidx);

		log("Input filename: %s\n", filename.c_str());

		ILANG_FRONTEND::lexin = f;
		ILANG_FRONTEND::current_design = design;
		rtlil_frontend_ilang_yydebug = false;
		rtlil_frontend_ilang_yyrestart(NULL);
		rtlil_frontend_ilang_yyparse();
		rtlil_frontend_ilang_yylex_destroy();

		std::unordered_set<std::string> module_set;
		struct stat file_stat;
		if((stat("module_info", &file_stat) != 0) || !S_ISREG(file_stat.st_mode))
		{
			std::ofstream fout("module_info");
			std::vector<RTLIL::Module*> sorted_modules;

			// extract module dependencies
			std::map<RTLIL::Module*, std::set<RTLIL::Module*>> module_deps;
			for (auto mod : design->modules()) {
				const std::string module_name = mod->name.c_str();
				module_set.insert(module_name);
				module_deps[mod] = std::set<RTLIL::Module*>();
				for (auto cell : mod->cells())
					if (design->has(cell->type))
						module_deps[mod].insert(design->module(cell->type));
			}

			// simple good-enough topological sort
			// (O(n*m) on n elements and depth m)
			while (module_deps.size() > 0) {
				size_t sorted_modules_idx = sorted_modules.size();
				for (auto &it : module_deps) {
					for (auto &dep : it.second)
						if (module_deps.count(dep) > 0)
							goto not_ready_yet;
					// log("Next in topological sort: %s\n", log_id(it.first->name));
					sorted_modules.push_back(it.first);
					not_ready_yet:;
				}
				if (sorted_modules_idx == sorted_modules.size())
					log_error("Cyclic dependency between modules found! Cycle includes module %s.\n", log_id(module_deps.begin()->first->name));
				while (sorted_modules_idx < sorted_modules.size())
					module_deps.erase(sorted_modules.at(sorted_modules_idx++));
			}

			std::unordered_map<std::string, std::string> module_map;
			std::unordered_map<std::string, std::string> parent_map;
			for (auto module : sorted_modules)
			{
				for (auto cell : module->cells())
				{
					const std::string cell_name = cell->name.c_str();
					const std::string cell_type = cell->type.c_str();
					if(module_set.find(cell_type) != module_set.end())
					{
						module_map.insert(std::make_pair(cell_type, cell_name));
						const std::string module_name = module->name.c_str();
						parent_map.insert(std::make_pair(cell_type, module_name));
					}
				}
			}

			int assert_id = 0, assume_id = 0, cover_id = 0;
			for (auto module : sorted_modules)
			{
				for (auto cell : module->cells())
				{
					if (cell->type.in(ID($assert), ID($assume), ID($cover)))
					{
						int &id = cell->type == ID($assert) ? assert_id :
							  cell->type == ID($assume) ? assume_id :
							  cell->type == ID($cover) ? cover_id : *(int*)nullptr;

						const std::string postfix = cell->type == ID($assert) ? "assertion" :
									    cell->type == ID($assume) ? "assumption" :
									    cell->type == ID($cover) ? "cover" : "";

						const std::string module_type = module->name.c_str();
						auto got_module = module_map.find(module_type);
						std::string module_name;
						if (got_module != module_map.end())
						{
							module_name = got_module->second;
						} else {
							module_name = module_type;
						}
						std::string parent_name;
						auto got_parent = parent_map.find(module_type);
						if (got_parent != parent_map.end())
						{
							parent_name = got_parent->second;
						}
						std::string info;
						if (cell->name[0] == '$' && cell->attributes.count(ID::src))
						{
							if (!parent_name.empty()) {
								info = parent_name.substr(1) + ".";
							}
							info += module_name.substr(1) + ": " + cell->attributes.at(ID::src).decode_string();
						} else {
							if (!parent_name.empty()) {
								info = parent_name.substr(1) + ".";
							}
							info += module_name.substr(1) + ": " + get_id(cell->name);
						}
						std::string complete_info = postfix + stringf(" %d %s", id, info.c_str());
						fout << complete_info << std::endl;
						id++;
					}
				}
			}


			fout.close();
			fout.clear();
		}
	}
} IlangFrontend;

YOSYS_NAMESPACE_END

